package com.treasure.service.commons.lang.util;

import org.springframework.beans.BeanUtils;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;

/**
 * Bean属性操作工具类
 * @author <a href="mailto:19919866605@163.com">Pan Xinwen</a>
 * @version 1.0 2018年5月29日 下午2:47:01
 */
public class BeanUtil {

	/**
	 * 不可变字段,此字段执行merge方法时不会被新值覆盖
	 */
	@Target({ElementType.FIELD})
	@Retention(RetentionPolicy.RUNTIME)
	@Documented
	public @interface ImmutableField {

	}



	/** 创建BeanCopier开销较大,缓存一下 */
	private static final Map<String, BeanCopier> cache = new ConcurrentHashMap<>();

	/** 创建合并工具缓存 */
	private static final Map<Class<?>, BeanInfo> mergeCache = new ConcurrentHashMap<>();

	public BeanUtil() {
	}

	/**
	 * 拷贝两个Bean对象之间的同名,同类型属性.
	 *
	 * @param src  源Bean
	 * @param dest 目标Bean
	 */
	public static void copy(Object src, Object dest) {
		if (src != null && dest != null) {
			String key = genKey(src, dest);
			BeanCopier copier = cache.get(key);
			if (copier == null) {
				//不用考虑多线程,无非也就是重复创建几个copier的问题
				copier = BeanCopier.create(src.getClass(), dest.getClass(), false);
				cache.put(key, copier);
			}
			copier.copy(src, dest, null);
		}
	}

	/**
	 * @param src 源对象
	 * @param supplier 目标对象创建器
	 * @param <T>
	 *
	 * @return
	 */
	public static <T> T copy(Object src, Supplier<T> supplier) {
		T t = supplier.get();
		copy(src,t);
		return t;
	}

	/**
	 * 将src中不为null的同名字段merge到dest中
	 *
	 * @param src
	 * @param dest
	 */
	public static void merge(Object src, Object dest) {
		if (src != null && dest != null) {
			BeanInfo srcBeanInfo = getBeanInfo(src.getClass());
			BeanInfo destBeanInfo = getBeanInfo(dest.getClass());

			for (Map.Entry<String, AccessMethod> entry : srcBeanInfo.accessMethodCache.entrySet()) {
				try {
					Object paramValue = entry.getValue().read(src);
					//如果源bean的属性值不为null才进行merge
					//这样可以保留目标bean中的原始字段
					if (paramValue != null) {
						AccessMethod accessMethod = destBeanInfo.getAccessMethod(entry.getKey());

						if (accessMethod != null) {
							accessMethod.write(dest,paramValue);
						}
					}
				} catch (InvocationTargetException| IllegalAccessException e) {
					throw new RuntimeException(e);
				}
			}
		}
	}

	private static BeanInfo getBeanInfo(Class<?> clazz) {
		BeanInfo beanInfo = mergeCache.get(clazz);
		if (beanInfo == null) {
			beanInfo = new BeanInfo(clazz);
			mergeCache.put(clazz, beanInfo);
		}
		return beanInfo;
	}

	/**
	 * 生成BeanCopier缓存key
	 *
	 * @param src
	 * @param dest
	 *
	 * @return
	 */
	private static String genKey(Object src, Object dest) {
		return StringUtil.buildMessage("{}_{}", src.getClass().getName(), dest.getClass().getName());
	}

	private static class BeanInfo {

		Map<String, AccessMethod> accessMethodCache = new HashMap<>();

		BeanInfo(Class<?> clazz) {
			PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(clazz);

			for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
				if("class".equals(propertyDescriptor.getName())) continue;
				boolean immutable = false;
				try {
					Field field = clazz.getDeclaredField(propertyDescriptor.getName());
					immutable = field.getAnnotation(ImmutableField.class) != null;
				} catch (NoSuchFieldException e) {
//					e.printStackTrace();
				}
				accessMethodCache.put(propertyDescriptor.getName(),
						new AccessMethod(propertyDescriptor.getReadMethod(), propertyDescriptor.getWriteMethod(),immutable));
			}
		}

		AccessMethod getAccessMethod(String propertyName) {
			return accessMethodCache.get(propertyName);
		}

	}

	private static class AccessMethod {

		Method readMethod;
		Method writeMethod;
		boolean immutable;

		AccessMethod(Method readMethod, Method writeMethod,boolean immutable) {
			this.readMethod = readMethod;
			this.writeMethod = writeMethod;
			this.immutable = immutable;
		}

		Object read(Object instance) throws InvocationTargetException, IllegalAccessException {
			return readMethod.invoke(instance);
		}

		void write(Object instance, Object... param) throws InvocationTargetException, IllegalAccessException {
			if(!immutable)
				writeMethod.invoke(instance, param);
		}

	}

	/**
	 * bean转换为map
	 * @param bean
	 * @param isAll 是否所有属性全部转换，还是只转换有值的
	 * @return
	 * @throws IntrospectionException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
     */
	public static Map<String,Object> convertBeanToMap(Object bean,boolean isAll)
			throws IntrospectionException, IllegalAccessException, InvocationTargetException {
		Class type = bean.getClass();
		Map returnMap = new HashMap();
		java.beans.BeanInfo beanInfo = Introspector.getBeanInfo(type);

		PropertyDescriptor[] propertyDescriptors =  beanInfo.getPropertyDescriptors();
		for (int i = 0; i< propertyDescriptors.length; i++) {
			PropertyDescriptor descriptor = propertyDescriptors[i];
			String propertyName = descriptor.getName();
			if (!propertyName.equals("class")) {
				Method readMethod = descriptor.getReadMethod();
				Object result = readMethod.invoke(bean, new Object[0]);
				if (result != null&&!result.equals("")) {
					returnMap.put(propertyName, result);
				} else {
					if(isAll)
					returnMap.put(propertyName, "");
				}
			}
		}
		return returnMap;
	}




    /**
     * bean转换为map
     * @param bean
     * @param isAll 是否所有属性全部转换，还是只转换有值的
     * @return
     * @throws IntrospectionException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    public static MultiValueMap<String, Object> convertBeanToMultiValueMap(Object bean,boolean isAll)
        throws IntrospectionException, IllegalAccessException, InvocationTargetException {
        Class type = bean.getClass();

        MultiValueMap<String, Object> returnMap = new LinkedMultiValueMap<>();
        java.beans.BeanInfo beanInfo = Introspector.getBeanInfo(type);

        PropertyDescriptor[] propertyDescriptors =  beanInfo.getPropertyDescriptors();
        for (int i = 0; i< propertyDescriptors.length; i++) {
            PropertyDescriptor descriptor = propertyDescriptors[i];
            String propertyName = descriptor.getName();
            if (!propertyName.equals("class")) {
                Method readMethod = descriptor.getReadMethod();
                Object result = readMethod.invoke(bean, new Object[0]);
                if (result != null&&!result.equals("")) {
                    returnMap.add(propertyName, result);
                } else {
                    if(isAll)
                        returnMap.add(propertyName, "");
                }
            }
        }
        return returnMap;
    }



	/**
	 * bean转换为map
	 * @param bean
	 * @return
	 * @throws IntrospectionException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
     */
	public static Map<String,Object> convertBeanToMap(Object bean)
			throws IntrospectionException, IllegalAccessException, InvocationTargetException
	{
		return convertBeanToMap(bean,true);
	}


    /**
     * bean转换为map
     * @param bean
     * @return
     * @throws IntrospectionException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    public static MultiValueMap<String, Object> convertBeanToMultiValueMap(Object bean)
        throws IntrospectionException, IllegalAccessException, InvocationTargetException
    {
        return convertBeanToMultiValueMap(bean,true);
    }

}
